﻿//-----------------------------------------------------------------------
// <copyright>
// This file is subject to the terms and conditions of the Microsoft Public License (MS-PL).
// See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL for more details.
// </copyright>
//----------------------------------------------------------------------- 
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Reflection;
using System.Text;
using Microsoft.VisualStudio.TextTemplating;

namespace Fulcrum
{
    class TextTemplatingEngineHost : ITextTemplatingEngineHost
    {
        List<CompilerError> errors;
        GenerationDataContext dataContext;

        public TextTemplatingEngineHost(GenerationDataContext dataContext)
        {
            this.errors = new List<CompilerError>();
            this.Errors = new ReadOnlyCollection<CompilerError>(this.errors);
            
            this.dataContext = dataContext;
        }

        public ICollection<CompilerError> Errors
        {
            get;
            private set;
        }

        public string OutputFileExtension
        {
            get;
            private set;
        }

#region ITextTemplatingEngineHost implementation

        // Summary:
        //     Gets a list of assembly references.
        //
        // Returns:
        //     An System.Collections.IList that contains assembly names.
        public IList<string> StandardAssemblyReferences
        {
            get
            {
                return new string[]
                {
                    //If this host searches standard paths and the GAC,
                    //we can specify the assembly name like this.
                    //---------------------------------------------------------
                    //"System"

                    //Because this host only resolves assemblies from the 
                    //fully qualified path and name of the assembly,
                    //this is a quick way to get the code to give us the
                    //fully qualified path and name of the System assembly.
                    //---------------------------------------------------------
                    typeof(System.Uri).Assembly.Location
                };
            }
        }

        //
        // Summary:
        //     Gets a list of namespaces.
        //
        // Returns:
        //     An System.Collections.IList that contains namespaces.
        public IList<string> StandardImports
        {
            get
            {
                return new string[]
                    {
                        "System"
                    };
            }
        }

        //
        // Summary:
        //     Gets the path and file name of the text template that is being processed.
        //
        // Returns:
        //     A System.String that contains the path and file name of the text template
        //     that is being processed.
        public string TemplateFile
        {
            get;
            set;
        }

        // 
        // Summary:
        //  Passes in the name of a service. If you have that service, you need to 
        //  pass back a pointer to it. Otherwise, you need to pass back NULL to 
        //  indicate that you have no knowledge of that service.
        public object GetHostOption(string optionName)
        {
            object returnObject;

            switch (optionName)
            {
                case "CacheAssemblies":
                    returnObject = true;
                    break;
                default:
                    returnObject = null;
                    break;
            }

            return returnObject;
        }

        //
        // Summary:
        //     Acquires the text that corresponds to a request to include a partial text
        //     template file.
        //
        // Parameters:
        //   requestFileName:
        //     The name of the partial text template file to acquire.
        //
        //   content:
        //     A System.String that contains the acquired text or System.String.Empty if
        //     the file could not be found.
        //
        //   location:
        //     A System.String that contains the location of the acquired text. If the host
        //     searches the registry for the location of include files or if the host searches
        //     multiple locations by default, the host can return the final path of the
        //     include file in this parameter. The host can set the location to System.String.Empty
        //     if the file could not be found or if the host is not file-system based.
        //
        // Returns:
        //     true to indicate that the host was able to acquire the text; otherwise, false.
        public bool LoadIncludeText(string requestFileName, out string content, out string location)
        {
            content = System.String.Empty;
            location = System.String.Empty;

            //If the argument is the fully qualified path of an existing file,
            //then we are done.
            //----------------------------------------------------------------
            if (File.Exists(requestFileName))
            {
                content = File.ReadAllText(requestFileName);
                return true;
            }

            //This can be customized to search specific paths for the file.
            //This can be customized to accept paths to search as command line
            //arguments.
            //----------------------------------------------------------------
            else
            {
                return false;
            }
        }

        //
        // Summary:
        //     Receives a collection of errors and warnings from the transformation engine.
        //
        // Parameters:
        //   errors:
        //     The System.CodeDom.Compiler.CompilerErrorCollection being passed to the host
        //     from the engine.
        public void LogErrors(CompilerErrorCollection errors)
        {
            foreach (CompilerError error in errors)
            {
                this.errors.Add(error);
            }
        }

        //
        // Summary:
        //     Provides an application domain to run the generated transformation class.
        //
        // Parameters:
        //   content:
        //     The contents of the text template file to be processed.
        //
        // Returns:
        //     An System.AppDomain that compiles and executes the generated transformation
        //     class.
        public AppDomain ProvideTemplatingAppDomain(string content)
        {
            GenerationDataContext.CurrentDataContext = this.dataContext;
            return AppDomain.CurrentDomain;
        }

        //
        // Summary:
        //     Allows a host to provide additional information about the location of an
        //     assembly.
        //
        // Parameters:
        //   assemblyReference:
        //     The assembly to resolve.
        //
        // Returns:
        //     A System.String that contains the specified assembly reference or the specified
        //     assembly reference with additional information.
        public string ResolveAssemblyReference(string assemblyReference)
        {
            //If the argument is the fully qualified path of an existing file,
            //then we are done. (This does not do any work.)
            //----------------------------------------------------------------
            if (File.Exists(assemblyReference))
            {
                return assemblyReference;
            }

            //Maybe the assembly is in the same folder as the text template that 
            //called the directive.
            //----------------------------------------------------------------
            string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile), assemblyReference);
            if (File.Exists(candidate))
            {
                return candidate;
            }

            //This can be customized to search specific paths for the file
            //or to search the GAC.
            //----------------------------------------------------------------

            //This can be customized to accept paths to search as command line
            //arguments.
            //----------------------------------------------------------------
            var assembly = Assembly.LoadWithPartialName(assemblyReference);
            //var assembly = Assembly.Load(assemblyReference);
            var location = assembly.Location;

            //If we cannot do better, return the original file name.
            return location;
        }


        //
        // Summary:
        //     Returns the type of a directive processor, given its friendly name.
        //
        // Parameters:
        //   processorName:
        //     The name of the directive processor to be resolved.
        //
        // Returns:
        //     The System.Type of the directive processor.
        public Type ResolveDirectiveProcessor(string processorName)
        {
            //This host will not resolve any specific processors.

            //If the directive processor cannot be found, throw an error.
            throw new ApplicationException("Directive Processor not found");
        }
        
        //
        // Summary:
        //     Resolves the value of a parameter for a directive processor if the parameter
        //     is not specified in the template text.
        //
        // Parameters:
        //   directiveId:
        //     The ID of the directive call to which the parameter belongs. This ID disambiguates
        //     repeated calls to the same directive from the same text template.
        //
        //   processorName:
        //     The name of the directive processor to which the directive belongs.
        //
        //   parameterName:
        //     The name of the parameter to be resolved.
        //
        // Returns:
        //     A System.String that represents the resolved parameter value.
        public string ResolveParameterValue(string directiveId, string processorName, string parameterName)
        {
            if (directiveId == null)
            {
                throw new ArgumentNullException("the directiveId cannot be null");
            }
            if (processorName == null)
            {
                throw new ArgumentNullException("the processorName cannot be null");
            }
            if (parameterName == null)
            {
                throw new ArgumentNullException("the parameterName cannot be null");
            }

            //Code to provide "hard-coded" parameter values goes here.
            //This code depends on the directive processors this host will interact with.

            //If we cannot do better, return the empty string.
            return String.Empty;
        }

        // 
        // Summary:
        //  A directive processor can call this method if a file name does not 
        //  have a path.
        //  The host can attempt to provide path information by searching 
        //  specific paths for the file and returning the file and path if found.
        public string ResolvePath(string path)
        {
            if (path == null)
            {
                throw new ArgumentNullException("path");
            }

            //If the argument is the fully qualified path of an existing file,
            //then we are done
            //----------------------------------------------------------------
            if (File.Exists(path))
            {
                return path;
            }

            //Maybe the file is in the same folder as the text template that 
            //called the directive.
            //----------------------------------------------------------------
            string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile), path);
            if (File.Exists(candidate))
            {
                return candidate;
            }

            //Look more places.
            //----------------------------------------------------------------
            //More code can go here...

            //If we cannot do better, return the original file name.
            return path;
        }
        
        /// <summary>
        /// Tells the host the extension specified by the template.
        /// </summary>
        /// <param name="extension">The file name extension for the generated text output</param>
        public void SetFileExtension(string extension)
        {
            this.OutputFileExtension = extension;
        }

        //
        // Summary:
        //     Tells the host the encoding that is expected for the generated text output.
        //
        // Parameters:
        //   encoding:
        //     The encoding for the generated text output.
        //
        //   fromOutputDirective:
        //     true to indicate that the user specified the encoding in the encoding parameter
        //     of the output directive.
        public void SetOutputEncoding(Encoding encoding, bool fromOutputDirective)
        {
            throw new NotImplementedException();
        }

#endregion ITextTemplatingEngineHost implementation
    }
}
